-- CIS 552, Mini-project
-- by Mike Gottlieb and Reilly Grant

-- |Module handling all interactions with the user.
module User(gameInfo, playerInfo, promptUser, printHelp,
            clearScreen, getPlayers, notifyDestitute, endGame) where

import qualified Card
import qualified Player
import qualified Poker
import Poker (Action(..))
import Control.Monad
import System.Console.Readline
import System.Exit
import List
import Util

-- |Print vital statistics about the game, like the contents of the pot,
--  and recent actions.
gameInfo :: Poker.GameState -> IO ()
gameInfo s = do putStr $ "Pot: " ++ (show $ Poker.completePot s) ++ "\n"
                putStr $ "Recent actions: " ++ (Poker.recentHistory s) ++ "\n"

-- |Print the user statistics seen at the beginning of their turn.
playerInfo :: Player.T -> [String] -> IO ()
playerInfo p a = do putStr $ show p
                    putStr $ "Actions: " ++ (unwords a) ++ "\n"

-- |Prompt the user to enter an action.
promptUser :: IO Action
promptUser = do maybeInput <- readline "> "
                let input = case maybeInput of
                              Nothing -> "exit"
                              Just s -> s
--              addHistory input -- information disclosure vulnerability
                action <- parseAction input
                case action of
                  Nothing -> promptUser
                  Just a -> return a

-- |Displays the help text.
printHelp :: IO ()
printHelp = 
    do putStr "bet n -- Make the amount to play n chips.\n"
       putStr "raise n -- Raise the amount to play by n chips.\n"
       putStr "see -- Match the current bet.\n"
       putStr "call -- Match the current bet and move to next round.\n"
       putStr "fold -- Give up and lose whatever money you put in the pot.\n"
       putStr "check -- Do nothing.\n"
       putStr "discard __cards__ -- Discards cards from your hand.\n __cards__"
       putStr " is defined as the text representation of your cards \n" 
       putStr "(e.g. 4H for 4 of Hearts) each separated by a space \n"
       putStr "exit -- Quit the current game.\n"        

-- |Parse a string from the user into an Action.  Displays proper error messages
--  when there are errors and returns Nothing.
parseAction :: String -> IO (Maybe Action)
parseAction s = 
    let ss = words s in
    if (length ss) == 0 then return Nothing
    else case (head ss) of
           "bet"     -> do bet <- parseOneNumber (tail ss)
                           case bet of
                             Nothing -> return Nothing
                             Just n -> return $ Just $ Bet n
           "raise"   -> do raise <- parseOneNumber (tail ss)
                           case raise of
                             Nothing -> return Nothing
                             Just n -> return $ Just $ Raise n
           "see"     -> return $ Just See
           "call"    -> return $ Just Call
           "fold"    -> return $ Just Fold
           "check"   -> return $ Just Check
           "discard" -> do cards <- parseCards (tail ss)
                           case cards of
                             Nothing -> return Nothing
                             Just cs -> return $ Just $ Discard (nub cs)
           "exit"    -> return $ Just Exit
           "help"    -> return $ Just Help
           _         -> do putStr $ "Invalid command: " ++ s ++ "\n"
                           return Nothing

-- |Do parsing and validation for an action expecting one number as a parameter.
parseOneNumber :: [String] -> IO (Maybe Int)
parseOneNumber s = 
    if (length s) /= 1
    then do putStr "One parameter expected.\n"
            return Nothing
    else let n = readMaybe (head s)
         in case n of
              Nothing -> do putStr $ "Invalid number: " ++ (head s) ++ "\n"
                            return Nothing
              Just n' -> return $ Just n'

-- |Do parsing and validation for an action expecting a list of cards as
--  parameters.
parseCards :: [String] -> IO (Maybe [Card.T])
parseCards [] = return $ Just []
parseCards (c:cs) = 
    do let card = Card.parse c
       case card of 
         Nothing -> do putStr $ "Invalid card: " ++ (show c) ++ "\n"
                       return Nothing
         Just card' -> do cards <- parseCards cs
                          case cards of
                            Nothing -> return Nothing
                            Just cards' -> return $ Just $ card' : cards'

-- |Generate a new list of players from names supplied by the user.
getPlayers :: IO [Player.T]
getPlayers = do x <- readline "Enter player name (enter when done): "
                case x of
                  Just "" -> return []
                  Nothing -> return []
                  Just name -> do rest <- getPlayers
                                  return $ Player.player name : rest

-- |Notify players who are out of the game because they couldn't ante up.
notifyDestitute :: [Player.T] -> IO ()
notifyDestitute [] = return ()
notifyDestitute (p:ps) = if not (Player.inPlay p)
                         then putStr $ Player.name p ++ " does not have enough"
                              ++ " money to ante up.\n"
                         else notifyDestitute ps

-- |Print enough newlines to clear the visible terminal.
clearScreen :: IO ()
clearScreen = replicateM_ 25 (putStr "\n")

-- |Prints the end of round info and prompts the user to play again.
endGame :: Poker.GameState -> IO Bool
endGame s = 
    let winners = map Player.name (filter Player.inPlay (Poker.players s))
        reward = Poker.completePot s `div` (length winners)
        sorted = sortBy (\x y -> compare (Poker.rankHand $ Player.hand y)
                                         (Poker.rankHand $ Player.hand x))
                        (Poker.players s)
    -- prints the players, their hands, and ranks of those hands and the winner
    in do sequence_ $ map (\x ->
                           do putStr $ Player.name x ++ ", $"
                              putStr $ (show $ Player.money x) ++ ", "
                              putStr $ unwords $ map show (Player.hand x)
                              putStr $ ", " ++ (show $ Poker.rankHand $
                                        Player.hand x)
                              if Player.inPlay x then putStr ", winner\n"
                                  else putStr "\n"
                          ) sorted
          answer <- readline "Do you want to play another round? (y/n) "
          case answer of
            Nothing  -> return False -- exit
            Just "n" -> return False -- exit
            Just "y" -> return True  -- reset
